Определение возраста покупателей

Исследовательский анализ данных

Первый столбец отвечает за имя файла, а второй - за целевой признак.

Посмотрим на фотографии из обучающей выборки.

Видим 32 картинки размером 224*224 пикселя. Каждый пиксель состоит из 3 значений: красный, зелёный, синий.

Используем Matplotlib для отображения фотографий.

Фотографии самые разнообразные:

Обучение модели

Мы не будем обучать модель здесь, а просто скопируем код, который задаёт её параметры. Сама модель будет обучена на GPU отдельно.


В качестве модели мы испытаем архитектуру ResNet50, предобученную на базе ImageNet. Верхушкой нашей модели будет полносвязный слой с одним нейроном: для предсказания единственного значения этого достаточно. Мы выставили количество эпох, равное 26: опытным путём мы установили, что примерно такого значения достаточно, чтобы получить отличное качество при валидации. В качестве функции потерь зададим MSE. Мы попробовали разные значения скорости обучения и выяснили, что значение 0.0001 вполне нам подходит, поскольку при длительном обучении и большем learning rate функция потерь начинает "застревать" и колебаться между похожими значениями - а значит, ходить кругами вокруг минимума, но не попадать в него.

Перенесите сюда код обучения модели и её результат вывода на экран.

(Код в этом разделе запускается в отдельном GPU-тренажёре, поэтому оформлен не как ячейка с кодом, а как код в текстовой ячейке)

Код модели

# импортируем необходимые объекты и модули
from tensorflow.keras.applications.resnet import ResNet50
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.layers              import GlobalAveragePooling2D, Dense
from tensorflow.keras.models              import Sequential
from tensorflow.keras.optimizers          import Adam

import numpy      as np
import pandas     as pd
import tensorflow as tf

# создание загрузчика для обучающей выборки с отражением фотографий по горизонтали
def load_train(path):
    labels = pd.read_csv(path + 'labels.csv')
    datagen = ImageDataGenerator(validation_split=0.25, horizontal_flip=True, rescale=1./255)
    train_gen_flow = datagen.flow_from_dataframe(
        dataframe=labels,
        directory=path + 'final_files/',
        x_col='file_name',
        y_col='real_age',
        target_size=(224, 224),
        batch_size=16,
        class_mode='raw',
        subset='training',
        seed=12345)

    return train_gen_flow

# создание загрузчика для тестовой выборки
def load_test(path):
    labels = pd.read_csv(path + 'labels.csv')
    datagen = ImageDataGenerator(validation_split=0.25, rescale=1./255)
    test_gen_flow = datagen.flow_from_dataframe(
        dataframe=labels,
        directory=path + 'final_files/',
        x_col='file_name',
        y_col='real_age',
        target_size=(224, 224),
        batch_size=16,
        class_mode='raw',
        subset='validation',
        seed=12345)

    return test_gen_flow


# создание модели
def create_model(input_shape):
    backbone = ResNet50(input_shape=input_shape,
                    weights='imagenet', 
                    include_top=False)
    model = Sequential()
    model.add(backbone)
    model.add(GlobalAveragePooling2D())
    model.add(Dense(1, activation='relu'))

    optimizer = Adam(lr=0.0001)
    model.compile(optimizer=optimizer, loss='mean_squared_error',
                  metrics=['mae'])
    return model

# параметры обучения
def train_model(model, train_data, test_data, batch_size=None, epochs=10,
                steps_per_epoch=None, validation_steps=None):

    if steps_per_epoch is None:
        steps_per_epoch = len(train_data)
    if validation_steps is None:
        validation_steps = len(test_data)

    model.fit(train_data,
              validation_data=test_data,
              batch_size=batch_size, 
              epochs=epochs,
              steps_per_epoch=steps_per_epoch,
              validation_steps=validation_steps,
              verbose=2)
    return model

Вывод консоли

Using TensorFlow backend.
Found 5694 validated image filenames.
Found 1897 validated image filenames.

<class 'tensorflow.python.keras.engine.sequential.Sequential'>

Train for 356 steps, validate for 119 steps
Epoch 1/10
2022-06-23 16:42:25.789817: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.10
2022-06-23 16:42:26.078268: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.7
356/356 - 48s - loss: 218.5762 - mae: 10.5990 - val_loss: 472.2291 - val_mae: 16.6123
Epoch 2/10
356/356 - 39s - loss: 84.7291 - mae: 6.9770 - val_loss: 130.5261 - val_mae: 8.7699
Epoch 3/10
356/356 - 39s - loss: 59.5567 - mae: 5.9253 - val_loss: 72.2174 - val_mae: 6.3433
Epoch 4/10
356/356 - 39s - loss: 41.5576 - mae: 4.9294 - val_loss: 85.0605 - val_mae: 6.9138
Epoch 5/10
356/356 - 38s - loss: 31.4504 - mae: 4.2790 - val_loss: 65.8210 - val_mae: 6.1303
Epoch 6/10
356/356 - 39s - loss: 24.9407 - mae: 3.7870 - val_loss: 73.1331 - val_mae: 6.4077
Epoch 7/10
356/356 - 39s - loss: 19.4462 - mae: 3.3749 - val_loss: 66.7495 - val_mae: 6.2473
Epoch 8/10
356/356 - 39s - loss: 15.7245 - mae: 3.0432 - val_loss: 69.7703 - val_mae: 6.2007
Epoch 9/10
356/356 - 39s - loss: 14.5598 - mae: 2.9097 - val_loss: 65.5894 - val_mae: 6.0967
Epoch 10/10
356/356 - 39s - loss: 13.1682 - mae: 2.7769 - val_loss: 66.0007 - val_mae: 6.2167
WARNING:tensorflow:sample_weight modes were coerced from
  ...
    to  
  ['...']
119/119 - 9s - loss: 66.0007 - mae: 6.2167
Test MAE: 6.2167

Вывод

Анализ обученной модели

*Средняя ошибка модели равна 6. Что это может значить?

Если мы предложим модели назвать возраст ребёнка на фотографии, то она вряд ли перепутает его с пожилым человеком. Сложно сказать, на людях каких возрастов модель ошибается больше всего. У нас нет возможности посмотреть на предсказания в силу реализации обучения, но если бы они у нас имелись, то мы могли бы категоризовать фотографии по возрасту и найти ошибку для каждой категории. В работу модели могут вносить случайность сами люди с фотографий: некоторые люди выглядят значительно старше своего возраста, другие - наоборот. Опять же, перед нами не стоит цель назвать точный возраст конкретного человека. Модель скорее оценивает фотографию по тем признакам, которые ей удалось усвоить, и говорит "человек с такой-то внешностью выглядит на такой-то возраст". Возможно, что в этом смысле модель ведёт себя подобно людям и ошибается в тех же местах, где ошибся бы человек.

*Будет ли от модели польза для бизнеса?

Если мы имеем ошибку в 6 лет, то рискуем не отличить взрослого от ребёнка в случае с продажей алкоголя. Паспорт всё так же придётся спрашивать. С рекомендацией товаров всё лучше: потребности людей не меняются кардинально раз в год, и у нас будет возможность довольно точно определять крупные возрастные категории - детей, подростков, молодых, зрелых, пенсионеров и изучать их потребности в автоматическом режиме. Допустим, если у нас есть предсказанный возраст человека и его чек, то мы можем совместить данные о транзакции с возрастом и сделать предположение о том, что тот или иной товар интересен аудитории.